using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Windows.Input;
using WindowsPhoneEssentials.Commands;
using WindowsPhoneEssentials.Diagnostics;
using WindowsPhoneMVC.ActionResults;
using WindowsPhoneMVC.Navigation.Interfaces;

namespace WindowsPhoneMVC.Navigation
{
    public class ControllerActions<TController> where TController : Controller
    {
        private readonly INavigator navigator;
        private readonly ViewModelBase viewModel;

        public ControllerActions(ViewModelBase viewModel)
        {
            this.viewModel = viewModel;
            if (viewModel == null) throw new ArgumentNullException("viewModel");
        }

        public ControllerActions(INavigator navigator)
        {
            this.navigator = navigator;
            if (navigator == null) throw new ArgumentNullException("navigator");
        }

        private INavigator Navigator
        {
            get { return navigator ?? viewModel.Navigator; }
        }

        public void Invoke(string actionName, params object[] parameters)
        {
            if (actionName == null) throw new ArgumentNullException("actionName");
            
            Navigator.Invoke<TController>(actionName, parameters);
        }

        public void Invoke(Expression<Func<TController, ActionResult>> actionSelector)
        {
            NavigateToExpression(actionSelector, r => Navigator.Invoke(r));
        }

        public void InvokeWithResult<TResult>(string actionName, Action<TResult> completeCallback, params object[] parameters)
        {
            if (actionName == null) throw new ArgumentNullException("actionName");
            Navigator.InvokeWithResult(typeof(TController).Name, actionName, completeCallback, parameters);
        }

        public void InvokeWithResult<TResult>(Expression<Func<TController, ActionResult>> actionSelector, Action<TResult> completeCallback)
        {
            NavigateToExpression(actionSelector, navRequest => Navigator.InvokeWithResult(navRequest, r => completeCallback((TResult)r)));
        }

        public void NavigateBackTo(string actionName)
        {
            Navigator.NavigateBackTo(typeof(TController).Name, actionName);
        }

        public ICommand InvokeWithResultCommand<TResult>(Expression<Func<TController, ActionResult>> actionSelector, Action<TResult> completeCallback)
        {
            return new DelegateCommand(() => InvokeWithResult(actionSelector, completeCallback));
        }

        public void Invoke<TArg>(Expression<Func<TController, Func<TArg, ActionResult>>> actionSelector, TArg arg)
        {
            if (actionSelector == null) throw new ArgumentNullException("actionSelector");

            var actionName = ActionName(actionSelector);

            Navigator.Invoke<TController>(actionName, arg);
        }

        public void Invoke<TArg, TArg2>(Expression<Func<TController, Func<TArg, TArg2, ActionResult>>> actionSelector, TArg arg, TArg2 arg2)
        {
            if (actionSelector == null) throw new ArgumentNullException("actionSelector");

            var actionName = ActionName(actionSelector);

            Navigator.Invoke<TController>(actionName, arg, arg2);
        }

        public void Invoke<TArg, TArg2, TArg3>(
            Expression<Func<TController, Func<TArg, TArg2, TArg3, ActionResult>>> actionSelector,
            TArg arg, TArg2 arg2, TArg3 arg3)
        {
            if (actionSelector == null) throw new ArgumentNullException("actionSelector");

            var actionName = ActionName(actionSelector);

            Navigator.Invoke<TController>(actionName, arg, arg2, arg3);
        }

        public ICommand InvokeCommand(Expression<Func<TController, ActionResult>> actionSelector)
        {
            return new DelegateCommand(() => Invoke(actionSelector));
        }

        public ICommand InvokeCommand<TArg>(Expression<Func<TController, Func<TArg, ActionResult>>> actionSelector, TArg arg)
        {
            return new DelegateCommand(() => Invoke(actionSelector, arg));
        }

        public ICommand InvokeCommand<TArg, TArg2>(Expression<Func<TController, Func<TArg, TArg2, ActionResult>>> actionSelector, TArg arg, TArg2 arg2)
        {
            return new DelegateCommand(() => Invoke(actionSelector, arg, arg2));
        }

        public ICommand InvokeCommand<TArg, TArg2, TArg3>(
            Expression<Func<TController, Func<TArg, TArg2, TArg3, ActionResult>>> actionSelector,
            TArg arg, TArg2 arg2, TArg3 arg3)
        {
            return new DelegateCommand(() => Invoke(actionSelector, arg, arg2, arg3));
        }

        public void InvokeBackground(Expression<Func<TController, Func<ActionResult>>> actionSelector)
        {
            if (actionSelector == null) throw new ArgumentNullException("actionSelector");

            var actionName = ActionName(actionSelector);

           Navigator.InvokeBackground<TController>(actionName);
        }

        public void InvokeBackground<TArg>(Expression<Func<TController, Func<TArg, ActionResult>>> actionSelector, TArg arg)
        {
            if (actionSelector == null) throw new ArgumentNullException("actionSelector");

            var actionName = ActionName(actionSelector);

            Navigator.InvokeBackground<TController>(actionName, arg);
        }

        public void InvokeBackground<TArg, TArg2>(Expression<Func<TController, Func<TArg, TArg2, ActionResult>>> actionSelector, TArg arg, TArg2 arg2)
        {
            if (actionSelector == null) throw new ArgumentNullException("actionSelector");

            var actionName = ActionName(actionSelector);

            Navigator.InvokeBackground<TController>(actionName, arg, arg2);
        }

        public void InvokeBackground<TArg, TArg2, TArg3>(
            Expression<Func<TController, Func<TArg, TArg2, TArg3, ActionResult>>> actionSelector,
            TArg arg, TArg2 arg2, TArg3 arg3)
        {
            if (actionSelector == null) throw new ArgumentNullException("actionSelector");

            var actionName = ActionName(actionSelector);

            Navigator.InvokeBackground<TController>(actionName, arg, arg2, arg3);
        }

        public ICommand InvokeBackgroundCommand(Expression<Func<TController, Func<ActionResult>>> actionSelector)
        {
            return new DelegateCommand(()=>InvokeBackground(actionSelector));
        }

        public ICommand InvokeBackgroundCommand<TArg>(Expression<Func<TController, Func<TArg, ActionResult>>> actionSelector, TArg arg)
        {
            return new DelegateCommand(() => InvokeBackground(actionSelector, arg));
        }

        public ICommand InvokeBackgroundCommand<TArg, TArg2>(Expression<Func<TController, Func<TArg, TArg2, ActionResult>>> actionSelector, TArg arg, TArg2 arg2)
        {
            return new DelegateCommand(() => InvokeBackground(actionSelector, arg, arg2));
        }

        public ICommand InvokeBackgroundCommand<TArg, TArg2, TArg3>(
            Expression<Func<TController, Func<TArg, TArg2, TArg3, ActionResult>>> actionSelector, 
            TArg arg, TArg2 arg2, TArg3 arg3)
        {
            return new DelegateCommand(() => InvokeBackground(actionSelector, arg, arg2, arg3));
        }

#if WINDOWS_PHONE71
        public string UriFor(Expression<Func<TController, Func<IDictionary<string, string>, ActionResult>>> actionSelector, IDictionary<string, string> parameters)
        {
            return DeepLink.UriFor(typeof (TController).Name, ActionName(actionSelector), parameters);
        }
#endif

        private void NavigateToExpression(Expression<Func<TController, ActionResult>> actionSelector, Action<NavigationRequest> navigate)
        {
            if (actionSelector == null) throw new ArgumentNullException("actionSelector");
            if (Navigator == null) 
                throw new InvalidOperationException("You cannot call Navigate from a viewmodel which was created in the current activate Controller Action, context is unavailable");

            var body = actionSelector.Body as MethodCallExpression;
            if (body == null)
                throw new Exception("The lambda expression used for navigation could not be parsed. The lambda should be a MethodCallExpression, for example: 'x => x.Search(Text)'.");

            NavigationRequest navigationRequest;
            using (TraceScope.Create(this, TraceLevel.Debug, () => "Parsing Navigation Expression"))
            {
                var parser = new NavigationExpressionParser(body);
                var controller = typeof(TController);
                var controllerName = controller.Name;
                navigationRequest = new NavigationRequest(Navigator.NavigationFrameName, controllerName, parser.ActionName, parser.GetNavigationParameters());
            }
            navigate(navigationRequest);
        }

        internal static string ActionName(LambdaExpression expression)
        {
            var unaryExpression = expression.Body as UnaryExpression;
            if (unaryExpression == null)
                throw ExpressionException();
            var methodCallExpression = unaryExpression.Operand as MethodCallExpression;
            if (methodCallExpression == null)
                throw ExpressionException();
            var arguments = methodCallExpression.Arguments as IList<Expression>;
            var methodInfoExpression = arguments.Single(s => s.Type == typeof(MethodInfo)) as ConstantExpression;
            if (methodInfoExpression == null)
                throw ExpressionException();
            var methodInfo = (MethodInfo)methodInfoExpression.Value;
            var actionName = methodInfo.Name;
            return actionName;
        }

        private static Exception ExpressionException()
        {
            throw new Exception("The lambda expression used for navigation could not be parsed.\r\n\r\n" +
                                "The lambda should be a expression using method grouping syntax, for example: 'x => x.Search'.");

        }
    }
}